package edu.spbsu.nonsm00th.model;

import edu.spbsu.nonsm00th.util.Precondition;
import java.util.Arrays;

/**
 * Vector representation class.
 *
 * @author mukhadin
 */
public class Vector {

    /** Coordinate array. */
    private double x[];

    /** Dimension of point space. */
    private int dimension;

    /**
     * Constructor for <code>Vector</code>.
     * Initializes <code>Vector</code> with two points.
     *
     * @param a the begin point
     * @param b the end point
     */
    public Vector(Point a, Point b) {
        Precondition.argsNotNull(a, b);
        Precondition.check(a.getDimension() == b.getDimension(), "Dimensions of two points should be equal");
        dimension = a.getDimension();
        x = new double[dimension];
        for (int i = 0; i < dimension; i++) {
            x[i] = b.getXi(i) - a.getXi(i);
        }
    }

    /**
     * Constructor for <code>Vector</code>.
     * Initializes <code>Vector</code> coordinates with values of <code>x</code>.
     *
     * @param x the values for initialization.
     */
    public Vector(double... x) {
        this.dimension = x.length;
        this.x = x;
    }

    /**
     * Calculates the cross product of two vectors.
     * Should be used only for 3-dimension vectors.
     *
     * @param a the first vector
     * @param b the second vector
     * @return the cross product
     */
    public static Vector crossProduct(Vector a, Vector b) {
        Precondition.check(3 == a.getDimension(), "Dimension of argument 'a' should be equal 3.");
        Precondition.check(3 == b.getDimension(), "Dimension of argument 'b' should be equal 3.");
        double x = a.getZ() * b.getY() - a.getY() * b.getZ();
        double y = a.getX() * b.getZ() - a.getZ() * b.getX();
        double z = a.getY() * b.getX() - a.getX() * b.getY();
        return new Vector(x, y, z);
    }

    /**
     * Calculates the dot product of two vectors.
     * Should be used only for 3-dimension vectors.
     * 
     * @param a the first vector
     * @param b the second vector
     * @return the dot product
     */
    public static double dotProduct(Vector a, Vector b) {
        return a.getX() * b.getX() + a.getY() * b.getY() + a.getZ() * b.getZ();
    }

    /**
     * Clculates the norm of vector.
     *
     * @return the norm
     */
    public double getNorm() {
        double d = 0.0;
        for (double xi : x) {
            d += xi * xi;
        }
        return Math.sqrt(d);
    }

    /**
     * Gets x-coordinate.
     * @return the value of x-coordinate.
     */
    public double getX() {
        return getXi(0);
    }

    /**
     * Sets x-coordinate.
     * @param x the value to set.
     */
    public void setX(double x) {
        setXi(0, x);
    }

    /**
     * Gets y-coordinate.
     * @return the value of y-coordinate.
     */
    public double getY() {
        return getXi(1);
    }

    /**
     * Sets y-coordinate.
     * @param y the value to set.
     */
    public void setY(double y) {
        setXi(1, y);
    }

    /**
     * Gets z-coordinate.
     * @return the value of z-coordinate.
     */
    public double getZ() {
        return getXi(2);
    }

    /**
     * Sets z-coordinate.
     * @param z the value to set.
     */
    public void setZ(double z) {
        setXi(2, z);
    }

    /**
     * Gets value of <code>i</code>-th coordinate.
     *
     * @param i the index of coordinate.
     * @return the value of coordinate.
     */
    public double getXi(int i) {
        Precondition.check(i < dimension, "Index must be less than " + dimension);
        return x[i];
    }

    /**
     * Sets <code>i</code>-th coordinate.
     *
     * @param i the index of coordinate.
     * @param value the value to set.
     */
    public void setXi(int i, double value) {
        Precondition.check(i < dimension, "Index must be less than " + dimension);
        this.x[i] = value;
    }

    /**
     * Gets the demension.
     *
     * @return the demension.
     */
    public int getDimension() {
        return dimension;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof Vector) {
            final Vector to = (Vector) obj;
            if (to.dimension != this.dimension) {
                return false;
            }
            for (int i = 0; i < to.dimension; i++) {
                if (to.x[i] != this.x[i]) {
                    return false;
                }
            }
            return true;
        }
        return false;
    }

    @Override
    public int hashCode() {
        int hash = 7;
        hash = 97 * hash + Arrays.hashCode(this.x);
        hash = 97 * hash + this.dimension;
        return hash;
    }

}
